#include "U2ArchiveFile.h"

#include "U2Exception.h"
#include "U2DataStreamFileStream.h"



#if U2_PLATFORM == U2_PLATFORM_LINUX || U2_PLATFORM == U2_PLATFORM_APPLE || \
    U2_PLATFORM == U2_PLATFORM_APPLE_IOS || \
    U2_PLATFORM == U2_PLATFORM_ANDROID || U2_PLATFORM == U2_PLATFORM_TEGRA2
#   include "U2SearchOps.h"
#   include <sys/param.h>
#   define MAX_PATH MAXPATHLEN
#endif

#if U2_PLATFORM == U2_PLATFORM_WIN32
#   define WIN32_LEAN_AND_MEAN
#   if !defined(NOMINMAX) && defined(_MSC_VER)
#       define NOMINMAX // required to stop windows.h messing up std::min
#   endif
#   include <windows.h>
#   include <direct.h>
#   include <io.h>
#endif



U2EG_NAMESPACE_USING




//-----------------------------------------------------------------------
static bool is_reserved_dir (const char *fn)
{
	return (fn [0] == '.' && (fn [1] == 0 || (fn [1] == '.' && fn [2] == 0)));
}
//-----------------------------------------------------------------------
static bool is_absolute_path(const char* path)
{
#if U2_PLATFORM == U2_PLATFORM_WIN32
	if (isalpha(u2uchar(path[0])) && path[1] == ':')
		return true;
#endif
	return path[0] == '/' || path[0] == '\\';
}
//-----------------------------------------------------------------------
static U2String concatenate_path(const U2String& base, const U2String& name)
{
	if (base.empty() || is_absolute_path(name.c_str()))
		return name;
	else
		return base + '/' + name;
}




bool U2ArchiveFile::msIgnoreHidden = true;

//-----------------------------------------------------------------------
U2ArchiveFile::U2ArchiveFile(const U2String& name, const U2String& archType )
: U2Archive(name, archType)
{
}
//-----------------------------------------------------------------------
bool U2ArchiveFile::isCaseSensitive(void) const
{
#if U2_PLATFORM == U2_PLATFORM_WIN32
    return false;
#else
    return true;
#endif

}
//-----------------------------------------------------------------------
void U2ArchiveFile::findFiles(const U2String& pattern, bool recursive, 
    bool dirs, U2StringVector* simpleList, FileInfoList* detailList) const
{
    long lHandle, res;
    struct _finddata_t tagData;

    // pattern can contain a directory name, separate it from mask
    size_t pos1 = pattern.rfind ('/');
    size_t pos2 = pattern.rfind ('\\');
    if (pos1 == pattern.npos || ((pos2 != pattern.npos) && (pos1 < pos2)))
        pos1 = pos2;
    U2String directory;
    if (pos1 != pattern.npos)
        directory = pattern.substr (0, pos1 + 1);

    U2String full_pattern = concatenate_path(mName, pattern);

    lHandle = _findfirst(full_pattern.c_str(), &tagData);
    res = 0;
    while (lHandle != -1 && res != -1)
    {
        if ((dirs == ((tagData.attrib & _A_SUBDIR) != 0)) &&
			( !msIgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0 ) &&
            (!dirs || !is_reserved_dir (tagData.name)))
        {
            if (simpleList)
            {
                simpleList->push_back(directory + tagData.name);
            }
            else if (detailList)
            {
                FileInfo fi;
                fi.archive = this;
                fi.filename = directory + tagData.name;
                fi.basename = tagData.name;
                fi.path = directory;
                fi.compressedSize = tagData.size;
                fi.uncompressedSize = tagData.size;
                detailList->push_back(fi);
            }
        }
        res = _findnext( lHandle, &tagData );
    }
    // Close if we found any files
    if(lHandle != -1)
        _findclose(lHandle);

    // Now find directories
    if (recursive)
    {
        U2String base_dir = mName;
        if (!directory.empty ())
        {
            base_dir = concatenate_path(mName, directory);
            // Remove the last '/'
            base_dir.erase (base_dir.length () - 1);
        }
        base_dir.append ("/*");

        // Remove directory name from pattern
        U2String mask ("/");
        if (pos1 != pattern.npos)
            mask.append (pattern.substr (pos1 + 1));
        else
            mask.append (pattern);

        lHandle = _findfirst(base_dir.c_str (), &tagData);
        res = 0;
        while (lHandle != -1 && res != -1)
        {
            if ((tagData.attrib & _A_SUBDIR) &&
				( !msIgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0 ) &&
                !is_reserved_dir (tagData.name))
            {
                // recurse
                base_dir = directory;
                base_dir.append (tagData.name).append (mask);
                findFiles(base_dir, recursive, dirs, simpleList, detailList);
            }
            res = _findnext( lHandle, &tagData );
        }
        // Close if we found any files
        if(lHandle != -1)
            _findclose(lHandle);
    }
}
//-----------------------------------------------------------------------
U2ArchiveFile::~U2ArchiveFile()
{
    unload();
}
//-----------------------------------------------------------------------
void U2ArchiveFile::load()
{
	U2_LOCK_AUTO_MUTEX
    // test to see if this folder is writeable
	U2String testPath = concatenate_path(mName, "__testwrite.ogre");
	std::ofstream writeStream;
	writeStream.open(testPath.c_str());
	if (writeStream.fail())
		mReadOnly = true;
	else
	{
		mReadOnly = false;
		writeStream.close();
		::remove(testPath.c_str());
	}
}
//-----------------------------------------------------------------------
void U2ArchiveFile::unload()
{
    // nothing to see here, move along
}
//-----------------------------------------------------------------------
U2DataStreamPtr U2ArchiveFile::open(const U2String& filename, bool readOnly) const
{
	U2String full_path = concatenate_path(mName, filename);

	// Use filesystem to determine size 
	// (quicker than streaming to the end and back)
	struct stat tagStat;
	int ret = stat(full_path.c_str(), &tagStat);
	assert(ret == 0 && "Problem getting file size" );
    (void)ret;  // Silence warning

	// Always open in binary mode
	// Also, always include reading
	std::ios::openmode mode = std::ios::in | std::ios::binary;
	std::istream* baseStream = 0;
	std::ifstream* roStream = 0;
	std::fstream* rwStream = 0;

	if (!readOnly && isReadOnly())
	{
		mode |= std::ios::out;
		rwStream = U2_NEW_T(std::fstream, MEMCATEGORY_GENERAL)();
		rwStream->open(full_path.c_str(), mode);
		baseStream = rwStream;
	}
	else
	{
		roStream = U2_NEW_T(std::ifstream, MEMCATEGORY_GENERAL)();
		roStream->open(full_path.c_str(), mode);
		baseStream = roStream;
	}


	// Should check ensure open succeeded, in case fail for some reason.
	if (baseStream->fail())
	{
		U2_DELETE_T(roStream, basic_ifstream, MEMCATEGORY_GENERAL);
		U2_DELETE_T(rwStream, basic_fstream, MEMCATEGORY_GENERAL);
		U2_EXCEPT(U2Exception::ERR_FILE_NOT_FOUND,
			"Cannot open file: " + filename,
			"U2ArchiveFile::open");
	}

	/// Construct return stream, tell it to delete on destroy
	U2DataStreamFileStream* stream = 0;
	if (rwStream)
	{
		// use the writeable stream 
		stream = U2_NEW U2DataStreamFileStream(filename,
			rwStream, (size_t)tagStat.st_size, true);
	}
	else
	{
		// read-only stream
		stream = U2_NEW U2DataStreamFileStream(filename,
			roStream, (size_t)tagStat.st_size, true);
	}
	return U2DataStreamPtr(stream);
}
//---------------------------------------------------------------------
U2DataStreamPtr U2ArchiveFile::create(const U2String& filename) const
{
	if (isReadOnly())
	{
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, 
			"Cannot create a file in a read-only archive", 
			"U2ArchiveFile::remove");
	}

	U2String full_path = concatenate_path(mName, filename);

	// Always open in binary mode
	// Also, always include reading
	std::ios::openmode mode = std::ios::out | std::ios::binary;
	std::fstream* rwStream = U2_NEW_T(std::fstream, MEMCATEGORY_GENERAL)();
	rwStream->open(full_path.c_str(), mode);

	// Should check ensure open succeeded, in case fail for some reason.
	if (rwStream->fail())
	{
		U2_DELETE_T(rwStream, basic_fstream, MEMCATEGORY_GENERAL);
		U2_EXCEPT(U2Exception::ERR_FILE_NOT_FOUND,
			"Cannot open file: " + filename,
			"U2ArchiveFile::create");
	}

	/// Construct return stream, tell it to delete on destroy
	U2DataStreamFileStream* stream = U2_NEW U2DataStreamFileStream(filename,
			rwStream, 0, true);

	return U2DataStreamPtr(stream);
}
//---------------------------------------------------------------------
void U2ArchiveFile::remove(const U2String& filename) const
{
	if (isReadOnly())
	{
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, 
			"Cannot remove a file from a read-only archive", 
			"U2ArchiveFile::remove");
	}
	U2String full_path = concatenate_path(mName, filename);
	::remove(full_path.c_str());

}
//-----------------------------------------------------------------------
U2StringVectorPtr U2ArchiveFile::list(bool recursive, bool dirs)
{
	// directory change requires locking due to saved returns
	// Note that we have to tell the SharedPtr to use U2_DELETE_T not U2_DELETE by passing category
	U2StringVectorPtr ret(U2_NEW_T(U2StringVector, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

    findFiles("*", recursive, dirs, ret.getPointer(), 0);

    return ret;
}
//-----------------------------------------------------------------------
FileInfoListPtr U2ArchiveFile::listFileInfo(bool recursive, bool dirs)
{
	// Note that we have to tell the SharedPtr to use U2_DELETE_T not U2_DELETE by passing category
    FileInfoListPtr ret(U2_NEW_T(FileInfoList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

    findFiles("*", recursive, dirs, 0, ret.getPointer());

    return ret;
}
//-----------------------------------------------------------------------
U2StringVectorPtr U2ArchiveFile::find(const U2String& pattern,
                                        bool recursive, bool dirs)
{
	// Note that we have to tell the SharedPtr to use U2_DELETE_T not U2_DELETE by passing category
	U2StringVectorPtr ret(U2_NEW_T(U2StringVector, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

    findFiles(pattern, recursive, dirs, ret.getPointer(), 0);

    return ret;

}
//-----------------------------------------------------------------------
FileInfoListPtr U2ArchiveFile::findFileInfo(const U2String& pattern, 
    bool recursive, bool dirs) const
{
	// Note that we have to tell the SharedPtr to use U2_DELETE_T not U2_DELETE by passing category
	FileInfoListPtr ret(U2_NEW_T(FileInfoList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

    findFiles(pattern, recursive, dirs, 0, ret.getPointer());

    return ret;
}
//-----------------------------------------------------------------------
bool U2ArchiveFile::exists(const U2String& filename)
{
    U2String full_path = concatenate_path(mName, filename);

    struct stat tagStat;
    bool ret = (stat(full_path.c_str(), &tagStat) == 0);

	// stat will return true if the filename is absolute, but we need to check
	// the file is actually in this archive
    if (ret && is_absolute_path(filename.c_str()))
	{
		// only valid if full path starts with our base
#if U2_PLATFORM == U2_PLATFORM_WIN32
		// case insensitive on windows
		U2String lowerCaseName = mName;
		U2StringUtil::toLowerCase(lowerCaseName);
		ret = U2StringUtil::startsWith(full_path, lowerCaseName, true);
#else
		// case sensitive
		ret = U2StringUtil::startsWith(full_path, mName, false);
#endif
	}

	return ret;
}
//---------------------------------------------------------------------
time_t U2ArchiveFile::getModifiedTime(const U2String& filename)
{
	U2String full_path = concatenate_path(mName, filename);

	struct stat tagStat;
	bool ret = (stat(full_path.c_str(), &tagStat) == 0);

	if (ret)
	{
		return tagStat.st_mtime;
	}
	else
	{
		return 0;
	}

}
//-----------------------------------------------------------------------
const U2String& U2ArchiveFactoryFile::getType(void) const
{
    static U2String name = "FileSystem";
    return name;
}